// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <bits/limits.h>
#include "trb-sizes.h"
#include "xhci-hw.h"
#include <ddk/io-buffer.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <memory>
#include <zircon/listnode.h>

namespace usb_xhci {

class IoBufferContainer;
// IO Buffer Container to allow nesting an IoBuffer
// in an intrusive doubly-linked list.
class IoBufferContainer : public fbl::DoublyLinkedListable<std::unique_ptr<IoBufferContainer>> {
 public:
  IoBufferContainer(ddk::IoBuffer buffer) : buffer_(std::move(buffer)) {}
  const ddk::IoBuffer* operator->() const { return &buffer_; }
  const ddk::IoBuffer& operator*() { return buffer_; }

 private:
  ddk::IoBuffer buffer_;
};

// Represents a virtual memory address mapping.
// Contains information about the virtual range,
// and physical starting address for contiguous mappings.
struct VirtualAddress {
  VirtualAddress() {}
  VirtualAddress(zx_vaddr_t virt_start, size_t virt_end)
      : virt_start(virt_start), virt_end(virt_end) {}
  VirtualAddress(size_t virt_start)
      : virt_start(virt_start), virt_end((virt_start + PAGE_SIZE) - 1) {}
  bool operator<(const VirtualAddress& other) const {
    return (virt_start / PAGE_SIZE) < (other.virt_start / PAGE_SIZE);
  }
  bool operator==(const VirtualAddress& other) const {
    return (virt_start / PAGE_SIZE) == (other.virt_start / PAGE_SIZE);
  }
  size_t GetKey() const { return virt_start / PAGE_SIZE; }
  zx_vaddr_t virt_start = 0;
  zx_vaddr_t virt_end = 0;
  size_t phys_start = 0;
};

// Constant-size map implementation with O(n) lookup time,
// and O(1) insertion time. Maintains a mapping of Keys to Values.
template <typename Key, typename Value, size_t Count>
class XhciMap {
 public:
  XhciMap() {}
  // Retrieves a given key, or creates one if it doesn't already exist
  // Asserts if the number of keys exceeds the statically-allocated buffer
  // size.
  Value& operator[](const Key& key) {
    for (size_t i = 0; i < len_; i++) {
      if (data_[i].first == key) {
        return data_[i].second;
      }
    }
    assert(len_ < Count);
    data_[len_].first = key;
    return data_[len_++].second;
  }
  // Retrieves the std::pair<Key, Value> for a given Key.
  // Returns nullptr if the key is not found in the map.
  std::pair<Key, Value>* get(const Key& key) {
    for (size_t i = 0; i < len_; i++) {
      if (data_[i].first == key) {
        return data_ + i;
      }
    }
    return nullptr;
  }
  // Removes all entries from this map
  void clear() {
    for (size_t i = 0; i < len_; i++) {
      data_[i] = {};
    }
    len_ = 0;
  }

 private:
  size_t len_ = 0;
  std::pair<Key, Value> data_[Count];
};

// used for both command ring and transfer rings
struct xhci_transfer_ring_t {
  fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
  XhciMap<VirtualAddress, uint64_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
  // Map of physical page indices to virtual addresses
  XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
  xhci_trb_t* start;
  xhci_trb_t* current_trb;  // next to be filled by producer
  uint8_t pcs;              // producer cycle status
  xhci_trb_t* dequeue_ptr;  // next to be processed by consumer
                            // (not used for command ring)
  size_t size;              // number of TRBs in ring
  bool full;                // true if there are no available TRBs,
                            // this is needed to differentiate between
                            // an empty and full ring state
  fbl::Mutex xfer_lock;
};

struct EventMapping {
  uint64_t phys = 0;
  xhci_trb_t* next = nullptr;
  EventMapping() {}
  EventMapping(uint64_t phys) : phys(phys) {}
};

struct xhci_event_ring_t {
  fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
  XhciMap<VirtualAddress, EventMapping, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
  XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
  xhci_trb_t* start;
  xhci_trb_t* current;
  xhci_trb_t* end;
  uint8_t ccs;  // consumer cycle status
  fbl::Mutex xfer_lock;
};

zx_status_t xhci_transfer_ring_init(xhci_transfer_ring_t* tr, zx_handle_t bti_handle, int count);
void xhci_transfer_ring_free(xhci_transfer_ring_t* ring);
size_t xhci_transfer_ring_free_trbs(xhci_transfer_ring_t* ring);
zx_status_t xhci_event_ring_init(xhci_event_ring_t*, zx_handle_t bti_handle,
                                 erst_entry_t* erst_array, int count);
void xhci_event_ring_free(xhci_event_ring_t* ring);
void xhci_clear_trb(xhci_trb_t* trb);
// Converts a transfer trb into a NO-OP transfer TRB, does nothing if it is the LINK TRB.
void xhci_set_transfer_noop_trb(xhci_trb_t* trb);
xhci_trb_t* xhci_read_trb_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
xhci_trb_t* xhci_next_evt(xhci_event_ring_t* ring, xhci_trb_t* trb);
xhci_trb_t* xhci_get_next_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
void xhci_increment_ring(xhci_transfer_ring_t* ring);
void xhci_set_dequeue_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* new_ptr);

// Returns the TRB corresponding to the given physical address, or nullptr if the address is
// invalid.
xhci_trb_t* xhci_transfer_ring_phys_to_trb(xhci_transfer_ring_t* ring, zx_paddr_t phys);

static inline zx_paddr_t xhci_transfer_ring_current_phys(xhci_transfer_ring_t* ring) {
  auto physmap =
      ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current_trb)));
  if (physmap == nullptr) {
    abort();
  }
  return physmap->second +
         (reinterpret_cast<size_t>(ring->current_trb) - physmap->first.virt_start);
}

static inline zx_paddr_t xhci_event_ring_current_phys(xhci_event_ring_t* ring) {
  auto physmap =
      ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current)));
  if (physmap == nullptr) {
    abort();
  }
  return physmap->second.phys +
         (reinterpret_cast<size_t>(ring->current) - physmap->first.virt_start);
}
struct xhci_t;
// Enlarges the XHCI rings. The caller must ensure exclusive ownership of the rings
// before invoking this function. Refer to XHCI 4.9.2.3
zx_status_t xhci_enlarge_ring(xhci_t* xhci, xhci_transfer_ring_t* transfer);

}  // namespace usb_xhci
